﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Xml;
using Catsland.Core;
using Catsland.Editor;
using System.ComponentModel;

namespace Catsland.CatsPlugin
{
	public class ParticleEmitter : CatComponent, Drawable
	{
		// emitter
		private Vector3 m_emitterSize;
        [CategoryAttribute("Emitter")]
        public Vector3 EmitterSize
        {
            get { return m_emitterSize; }
            set { m_emitterSize = value; }
        }

		private Vector3 m_emitterOffset;
        [CategoryAttribute("Emitter")]
        public Vector3 EmitterOffset
        {
            get { return m_emitterOffset; }
            set { m_emitterOffset = value; }
        }

		private int m_generateRate; // num/second
        [CategoryAttribute("Emitter")]
        public int GenerateRate
        {
            get { return m_generateRate; }
            set { m_generateRate = value; }
        }

		private int m_maxParticles;
        [CategoryAttribute("Emitter")]
        public int MaxParticales
        {
            get { return m_maxParticles; }
            set 
            { 
                m_maxParticles = value;
                Initialize(Mgr<Scene>.Singleton);
            }
        }
		// speed
		private Vector3 m_VelocityDirection;
        [CategoryAttribute("Emitter")]
        public Vector3 VelocityDirection
        {
            get { return m_VelocityDirection; }
            set { SetVelocityDirection(value); }
        }

		private float m_biasTheta;
        [CategoryAttribute("Emitter")]
        public float BiasTheta
        {
            get { return m_biasTheta; }
            set { SetVelocityBias(value); }
        }
		private float m_biasRadius;

		private float m_speed;
        [CategoryAttribute("Particle")]
        public float Speed
        {
            get { return m_speed; }
            set { m_speed = value; }
        }

		private float m_speedBiaz;
        [CategoryAttribute("Particle")]
        public float SpeedBias
        {
            get { return m_speedBiaz; }
            set { m_speedBiaz = value; }
        }

		// particle
		private Vector2 m_particleSize;
        [CategoryAttribute("Particle")]
        public Vector2 ParticleSize
        {
            get { return m_particleSize; }
            set { m_particleSize = value; }
        }

		private Material m_material;
        [CategoryAttribute("Particle"),
         EditorAttribute(typeof(PropertyGridMaterialSelector),
         typeof(System.Drawing.Design.UITypeEditor))]
        public Material ParticleMaterial
        {
            get { return m_material; }
            set { m_material = value; }
        }

		private int m_lifetime;
        [CategoryAttribute("Particle")]
        public int ParicleLifeTime
        {
            get { return m_lifetime; }
            set { m_lifetime = value; }
        }

		// play
		public bool m_isEmitting;
		bool m_isXMirror = false;
		bool m_isYMirror = false;

		// inner
		Vector3[] m_particlePositions;
		Vector3[] m_particleVelocities;
		int[] m_lifetimes;
		int m_nextExpireIndex;
		int m_generateIndex;
		int m_accumulateMillionSecond;
		Random m_random;

		VertexBuffer m_vertexBuffer;
		VertexPositionColorTexture[] m_vertex;

		public ParticleEmitter(GameObject gameObject)
			: base(gameObject)
		{
			m_emitterSize = new Vector3(0.1f, 0.1f, 0.1f);
			m_emitterOffset = Vector3.Zero;
			m_generateRate = 8;
			m_maxParticles = 64;

			m_VelocityDirection = new Vector3(0.0f, 0.0f, 1.0f);
			SetVelocityBias(45.0f);
			m_speed = 0.1f;
			m_speedBiaz = 0.0f;

			m_particleSize = new Vector2(0.1f, 0.1f);
			
			m_lifetime = 1000;
			m_isEmitting = true;
			m_isXMirror = false;
			m_isYMirror = false;
		}

		public override CatComponent CloneComponent(GameObject gameObject)
		{
			ParticleEmitter particleEmitter = new ParticleEmitter(gameObject);

			particleEmitter.m_emitterSize = new Vector3(m_emitterSize.X, m_emitterSize.Y, m_emitterSize.Z);
			particleEmitter.m_emitterOffset = new Vector3(m_emitterOffset.X, m_emitterOffset.Y, m_emitterOffset.Z);
			particleEmitter.m_generateRate = m_generateRate;
			particleEmitter.m_maxParticles = m_maxParticles;
			
			particleEmitter.m_VelocityDirection = new Vector3(m_VelocityDirection.X, 
				m_VelocityDirection.Y, m_VelocityDirection.Z);
			particleEmitter.m_biasTheta = m_biasTheta;
			particleEmitter.m_biasRadius = m_biasRadius;
			particleEmitter.m_speed = m_speed;
			particleEmitter.m_speedBiaz = m_speedBiaz;
			
			particleEmitter.m_particleSize = new Vector2(m_particleSize.X, m_particleSize.Y);
			particleEmitter.m_material = m_material;
			particleEmitter.m_lifetime = m_lifetime;

			particleEmitter.m_isEmitting = m_isEmitting;
			particleEmitter.m_isXMirror = m_isXMirror;
			particleEmitter.m_isYMirror = m_isYMirror;

			return particleEmitter;
		}

		public override void BindToScene(Scene scene)
		{
			base.BindToScene(scene);

			scene._renderList.AddItem(this);
		}

		// all the parameters should be set before init
		public override void Initialize(Scene scene)
		{
			base.Initialize(scene);
			m_random = new Random();

			// check if the actual particle number will exceed maxParticles
			if (m_lifetime * m_generateRate > m_maxParticles)
			{
				Console.WriteLine("Warning! More need greater maxParticle limitation.");
			}
			// apply for particles
			int i;
			m_particlePositions = new Vector3[m_maxParticles];
			m_particleVelocities = new Vector3[m_maxParticles];
			m_lifetimes = new int[m_maxParticles];
			for(i=0; i < m_maxParticles; ++i)
			{
				m_particlePositions[i] = Vector3.Zero;
				m_particleVelocities[i] = Vector3.Zero;
				m_lifetimes[i] = 0;
			}


			// apply for vertex
			m_vertex = new VertexPositionColorTexture[m_maxParticles * 4];
			Vector2[] texCoord = new Vector2[4];
			texCoord[0] = new Vector2(0.0f, 1.0f);
			texCoord[1] = new Vector2(0.0f, 0.0f);
			texCoord[2] = new Vector2(1.0f, 1.0f);
			texCoord[3] = new Vector2(1.0f, 0.0f);
			
			for (i = 0; i < m_maxParticles * 4; ++i)
			{
				m_vertex[i] = new VertexPositionColorTexture(
					new Vector3(0.0f, 0.0f, 0.0f),
					Color.White,
					texCoord[i%4]);
			}
			m_vertexBuffer = new VertexBuffer(Mgr<GraphicsDevice>.Singleton, typeof(VertexPositionColorTexture),
					m_maxParticles *4, BufferUsage.None);
			m_vertexBuffer.SetData<VertexPositionColorTexture>(m_vertex);
			
			m_nextExpireIndex = 0;
			m_generateIndex = 0;
			m_accumulateMillionSecond = 0;

			if (scene != null)
			{
				scene._renderList.AddItem(this);
			}
		}

		public override void Update(int timeLastFrame)
		{
			base.Update(timeLastFrame);

			int i;
			// generate
			if (m_isEmitting)
			{
				m_accumulateMillionSecond += timeLastFrame;
				int generateNum = (int)(m_accumulateMillionSecond * m_generateRate / 1000.0);
				m_accumulateMillionSecond -= (int)(generateNum * 1000 / (float)m_generateRate);

				GenerateParticles(generateNum);
			}

			// update living particle
			// go life
			for (i = m_nextExpireIndex; i != m_generateIndex; i = (i + 1) % m_maxParticles)
			{
				m_lifetimes[i] -= timeLastFrame;
			}

			while (m_lifetimes[m_nextExpireIndex] < 0)
			{
				if (m_nextExpireIndex != m_generateIndex)
				{
					m_nextExpireIndex = (m_nextExpireIndex + 1) % m_maxParticles;
				}
				else
				{
					break;
				}
			}

			//Console.WriteLine("" + m_nextExpireIndex + "," + m_generateIndex);
			// update position
			float lastTimeSecond = timeLastFrame / 1000.0f;
			for (i = m_nextExpireIndex; i != m_generateIndex; i = (i + 1) % m_maxParticles)
			{
				m_particlePositions[i] += m_particleVelocities[i] * lastTimeSecond;

				m_vertex[4 * i].Position.X = m_particlePositions[i].X - m_particleSize.X / 2.0f;
				m_vertex[4 * i].Position.Y = m_particlePositions[i].Y * Mgr<Scene>.Singleton._yCos
					+ m_particlePositions[i].Z * Mgr<Scene>.Singleton._ySin - m_particleSize.Y / 2.0f;

				m_vertex[4 * i + 1].Position.X = m_particlePositions[i].X - m_particleSize.X / 2.0f;
				m_vertex[4 * i + 1].Position.Y = m_particlePositions[i].Y * Mgr<Scene>.Singleton._yCos
					+ m_particlePositions[i].Z * Mgr<Scene>.Singleton._ySin + m_particleSize.Y / 2.0f;

				m_vertex[4 * i + 2].Position.X = m_particlePositions[i].X + m_particleSize.X / 2.0f;
				m_vertex[4 * i + 2].Position.Y = m_particlePositions[i].Y * Mgr<Scene>.Singleton._yCos
					+ m_particlePositions[i].Z * Mgr<Scene>.Singleton._ySin - m_particleSize.Y / 2.0f;

				m_vertex[4 * i + 3].Position.X = m_particlePositions[i].X + m_particleSize.X / 2.0f;
				m_vertex[4 * i + 3].Position.Y = m_particlePositions[i].Y * Mgr<Scene>.Singleton._yCos
					+ m_particlePositions[i].Z * Mgr<Scene>.Singleton._ySin + m_particleSize.Y / 2.0f;
			}
		}

		public void SetVelocityDirection(Vector3 direction)
		{
			if (direction.LengthSquared() == 0.0f)
			{
				Console.WriteLine("Error! Zero direction.");
				return;
			}

			m_VelocityDirection = direction;
			m_VelocityDirection.Normalize();
		}

		public Vector3 GetVelocityDirection()
		{
			return m_VelocityDirection;
		}

		public void SetVelocityBias(float theta)
		{
			// m_velocityDirection should be normalized
			m_biasTheta = theta;
			m_biasRadius = (float)Math.Sin(theta * MathHelper.Pi/180.0f);
		}

		public float GetVelocityBias()
		{
			return m_biasTheta;
		}

		public void Draw(int timeLastFrame)
		{
			// now just take it as basic effect
			Mgr<GraphicsDevice>.Singleton.SetVertexBuffer(m_vertexBuffer);

			BasicEffect effect = (BasicEffect)(m_material._effect);
			effect.World = Matrix.Identity;
			effect.View = Mgr<Camera>.Singleton.m_view;
			effect.Projection = Mgr<Camera>.Singleton.m_projection;
			effect.TextureEnabled = true;
			effect.VertexColorEnabled = false;
			effect.Texture = m_material._texture;

			int i;
			for (i = m_nextExpireIndex; i != m_generateIndex; i = (i + 1) % m_maxParticles)
			{
				effect.Alpha = m_lifetimes[i] / (float)m_lifetime;
				foreach (EffectPass pass in effect.CurrentTechnique.Passes)
				{
					pass.Apply();
					Mgr<GraphicsDevice>.Singleton.DrawUserPrimitives<VertexPositionColorTexture>(
						PrimitiveType.TriangleStrip,
						m_vertex,
						i * 4,
						2);
				}
			}
		}

		public void SetLifeTime(float second)
		{
			m_lifetime = (int)(second * 1000);
			if (m_lifetime * m_generateRate > m_maxParticles)
			{
				Console.WriteLine("Warning! More need greater maxParticle limitation.");
			}
		}

		public void OneShot(int number)
		{
			m_isEmitting = false;
			GenerateParticles(number);
		}

		void GenerateParticles(int number)
		{
			int i;
			for (i = 0; i < number; ++i)
			{
				if ((m_generateIndex + 1) % m_maxParticles == m_nextExpireIndex)
				{
					// full
					break;
				}
				// random position
				m_particlePositions[m_generateIndex] = 
					new Vector3(m_gameObject.Position, m_gameObject.Height) 
					+ m_emitterOffset
					+ (new Vector3((float)m_random.NextDouble(), (float)m_random.NextDouble(), (float)m_random.NextDouble()) * m_emitterSize);
				// random velocity
				Vector3 randomDirection = m_VelocityDirection 
					+ new Vector3((float)m_random.NextDouble() - 0.5f,(float)m_random.NextDouble() - 0.5f,(float)m_random.NextDouble() - 0.5f) * 2.0f * m_biasRadius;
 				randomDirection.Normalize();
				m_particleVelocities[m_generateIndex] = randomDirection * (m_speed + ((float)m_random.NextDouble() - 0.5f) * 2.0f * m_speedBiaz);
				// init age
				m_lifetimes[m_generateIndex] = m_lifetime;

				// add
				m_generateIndex = (m_generateIndex + 1) % m_maxParticles;
			}
		}

		void AppendSingleTextAttribute(XmlNode parent, String name, String value, XmlDocument doc)
		{
			XmlElement node = doc.CreateElement(name);
			parent.AppendChild(node);
			node.InnerText = value;
		}

		static String GetFromSingleTextAttribute(XmlNode parent, String name)
		{
			return ((XmlElement)parent.SelectSingleNode(name)).InnerText;
		}

		public override bool SaveToNode(XmlNode node, XmlDocument doc)
		{
			XmlElement particleEmitter = doc.CreateElement("ParticleEmitter");
			node.AppendChild(particleEmitter);

			XmlElement emitterSize = doc.CreateElement("EmitterSize");
			particleEmitter.AppendChild(emitterSize);
			emitterSize.SetAttribute("X", "" + m_emitterSize.X);
			emitterSize.SetAttribute("Y", "" + m_emitterSize.Y);
			emitterSize.SetAttribute("Z", "" + m_emitterSize.Z);

			XmlElement emitterOffset = doc.CreateElement("EmitterOffset");
			particleEmitter.AppendChild(emitterOffset);
			emitterOffset.SetAttribute("X", "" + m_emitterOffset.X);
			emitterOffset.SetAttribute("Y", "" + m_emitterOffset.Y);
			emitterOffset.SetAttribute("Z", "" + m_emitterOffset.Z);

			AppendSingleTextAttribute(particleEmitter, "GenerateRate", "" + m_generateRate, doc);
			AppendSingleTextAttribute(particleEmitter, "MaxParticle", "" + m_maxParticles, doc);

			XmlElement velocityDirection = doc.CreateElement("VelocityDirection");
			particleEmitter.AppendChild(velocityDirection);
			velocityDirection.SetAttribute("X", "" + m_VelocityDirection.X);
			velocityDirection.SetAttribute("Y", "" + m_VelocityDirection.Y);
			velocityDirection.SetAttribute("Z", "" + m_VelocityDirection.Z);

			AppendSingleTextAttribute(particleEmitter, "BiasTheta", "" + m_biasTheta, doc);
			AppendSingleTextAttribute(particleEmitter, "Speed", "" + m_speed, doc);
			AppendSingleTextAttribute(particleEmitter, "BiasSpeed", "" + m_speedBiaz, doc);

			XmlElement particleSize = doc.CreateElement("ParticleSize");
			particleEmitter.AppendChild(particleSize);
			particleSize.SetAttribute("Width", "" + m_particleSize.X);
			particleSize.SetAttribute("Height", "" + m_particleSize.Y);

			AppendSingleTextAttribute(particleEmitter, "Material", m_material._name, doc);
			AppendSingleTextAttribute(particleEmitter, "Lifetime", "" + m_lifetime, doc);
			AppendSingleTextAttribute(particleEmitter, "IsEmitting", "" + m_isEmitting, doc);

			return true;
		}

        public override void ConfigureFromNode(XmlElement node, Scene scene, GameObject gameObject)
        {
            base.ConfigureFromNode(node, scene, gameObject);

            // emitter size
            XmlElement emitterSize = (XmlElement)node.SelectSingleNode("EmitterSize");
            m_emitterSize = new Vector3(float.Parse(emitterSize.GetAttribute("X")),
                                                        float.Parse(emitterSize.GetAttribute("Y")),
                                                        float.Parse(emitterSize.GetAttribute("Z")));

            // emitter offset
            XmlElement emitterOffset = (XmlElement)node.SelectSingleNode("EmitterOffset");
            m_emitterOffset = new Vector3(float.Parse(emitterOffset.GetAttribute("X")),
                                                        float.Parse(emitterOffset.GetAttribute("Y")),
                                                        float.Parse(emitterOffset.GetAttribute("Z")));

            m_generateRate = int.Parse(GetFromSingleTextAttribute(node, "GenerateRate"));
            m_maxParticles = int.Parse(GetFromSingleTextAttribute(node, "MaxParticle"));

            // velocity direction
            XmlElement velocityDirection = (XmlElement)node.SelectSingleNode("VelocityDirection");
            Vector3 newVelocityDirection = new Vector3(float.Parse(velocityDirection.GetAttribute("X")),
                                                            float.Parse(velocityDirection.GetAttribute("Y")),
                                                            float.Parse(velocityDirection.GetAttribute("Z")));
            SetVelocityDirection(newVelocityDirection);

            float biasTheta = float.Parse(GetFromSingleTextAttribute(node, "BiasTheta"));
            SetVelocityBias(biasTheta);
            m_speed = float.Parse(GetFromSingleTextAttribute(node, "Speed"));
            m_speedBiaz = float.Parse(GetFromSingleTextAttribute(node, "BiasSpeed"));

            // particle size
            XmlElement particleSize = (XmlElement)node.SelectSingleNode("ParticleSize");
            m_particleSize = new Vector2(float.Parse(particleSize.GetAttribute("Width")),
                                                    float.Parse(particleSize.GetAttribute("Height")));

            m_material = scene._materialList.GetMaterial(GetFromSingleTextAttribute(node, "Material"));
            m_lifetime = int.Parse(GetFromSingleTextAttribute(node, "Lifetime"));
            m_isEmitting = bool.Parse(GetFromSingleTextAttribute(node, "IsEmitting"));
        }

		public override void Destroy()
		{
			Mgr<Scene>.Singleton._renderList.RemoveItem(this);
			base.Destroy();
		}

		public float GetDepth()
		{
			return m_gameObject.Position.Y + m_emitterOffset.Y;
		}

		public int CompareTo(object obj)
		{
			float otherDepth = ((Drawable)obj).GetDepth();
			float thisDepth = GetDepth();
			if (otherDepth > thisDepth)
			{
				return 1;
			}
			else if (otherDepth < thisDepth)
			{
				return -1;
			}
			return 0;
		}
	}
}
